package tmhprediction.classification;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Vector;

import tmhprediction.eval.SolTmhEvaluator;
import tmhprediction.main.TMHResultMap;
import weka.classifiers.Evaluation;
import weka.classifiers.functions.LibSVM;
import weka.core.Attribute;
import weka.core.Instances;
import weka.core.SerializationHelper;
import weka.core.converters.ConverterUtils.DataSource;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.RemoveType;



public class TMHClassifier {
	
	public static final double PARSEDSYMBOLTMHRESIDUE = 1.0;
	public static final double PARSEDSYMBOLNONTMHRESIDUE = 0.0;
	
	// TODO: should be changed: transmembrane = 1 and other = 0; but first check for side-effects
	public static final int TRANSMEMBRANE_INSTANCES = 0;
	public static final int SOLUBLE_INSTANCES = 1;
	
    private LibSVM classifier;
    private RemoveType filter;
    private Instances testSet;
    private Evaluation evaluation;
    private Instances filteredTestSet;
    
    /**
     * Train only. Used in 'Program1'
     * @param trainingPath
     * @throws Exception 
     */
    public TMHClassifier(String trainingPath, double gamma, double cost, int cacheSize) throws Exception
    {
    	classifier = new LibSVM();
    	filter = new RemoveType();
    	String[] filterOptions = { "-T", "string" };
    	filter.setOptions(filterOptions);
    	Instances temp = DataSource.read(trainingPath);
    	filter.setInputFormat(temp);
    	this.buildClassifier(trainingPath, gamma, cost, cacheSize);
    }
    
    public TMHClassifier(String trainingPath, String testPath,
	    double gamma, double cost) throws Exception {
	classifier = new LibSVM();
	this.testSet = DataSource.read(testPath);
	testSet.setClass(testSet.attribute(testSet.numAttributes() - 1));
	filter = new RemoveType();
	String[] filterOptions = { "-T", "string" };
	filter.setOptions(filterOptions);	
	filter.setInputFormat(testSet);
	this.filteredTestSet = filterSet(testPath);
	this.buildClassifier(trainingPath, gamma, cost, 40);
//	this.evaluation = createEvaluation();
    }
    
    public TMHClassifier(String trainingPath, String testPath,
    	    double gamma, double cost, int pCacheSize) throws Exception {
    	classifier = new LibSVM();
    	this.testSet = DataSource.read(testPath);
    	testSet.setClass(testSet.attribute(testSet.numAttributes() - 1));
    	filter = new RemoveType();
    	String[] filterOptions = { "-T", "string" };
    	filter.setOptions(filterOptions);	
    	filter.setInputFormat(testSet);
    	this.filteredTestSet = filterSet(testPath);
    	this.buildClassifier(trainingPath, gamma, cost, pCacheSize);
//    	this.evaluation = createEvaluation();
        }

    public TMHClassifier(String modelPath, String testPath) throws Exception {
	loadModel(modelPath);
	this.testSet = DataSource.read(testPath);
	testSet.setClass(testSet.attribute(testSet.numAttributes() - 1));
	filter = new RemoveType();
	String[] filterOptions = { "-T", "string" };
	filter.setOptions(filterOptions);
	filter.setInputFormat(testSet);
	this.filteredTestSet = filterSet(testPath);
	this.evaluation = createEvaluation();
    }
    
    // used after a svm was applied
    
    public TMHClassifier(String modelPath, Instances testSet) throws Exception {
    	this(modelPath,testSet,40.0);
    }
   
    public TMHClassifier(String modelPath, Instances testSet, double cacheSize) throws Exception {
    	loadModel(modelPath);
    	classifier.setCacheSize(cacheSize);
    	this.testSet = testSet;
    	testSet.setClass(testSet.attribute(testSet.numAttributes() - 1));
    	
    	//Filtering
    	filter = new RemoveType();
    	String[] filterOptions = { "-T", "string" };
    	filter.setOptions(filterOptions);
    	filter.setInputFormat(testSet);
    	
    	this.filteredTestSet = filterSetInstances(testSet);
//    	this.evaluation = createEvaluation();
        }
    
    private Instances filterSetInstances(Instances unfilteredSet)
    {
    	try {
//    	    Instances filteredSet = new Instances(unfilteredSet);
    	    Instances result = Filter.useFilter(unfilteredSet, filter);		// TODO not completely sure this really copies? But why return it if done by reference, should work; by stefan: it does :-)
    	    result.setClass(result.attribute(result.numAttributes() - 1));
    	    return result;
    	} catch (Exception e) {
    	    throw new RuntimeException(e);
    	}
    }
    
    public void buildClassifier(String trainingPath, double gamma, double cost, int pCacheSize) {
	try {
	    classifier.setGamma(gamma);
	    classifier.setCost(cost);
	    classifier.setProbabilityEstimates(true);
	    classifier.setCacheSize(pCacheSize);
	    classifier.buildClassifier(filterSet(trainingPath));
//	    classifier.setWeights("3.0 1.0");
	} catch (Exception ex) {
		ex.printStackTrace();
	    throw new RuntimeException();
	}
    }

    private Instances filterSet(String path) {
	try {
	    Instances set = DataSource.read(path);
	    Instances result = Filter.useFilter(set, filter);
	    result.setClass(result.attribute(result.numAttributes() - 1));
	    return result;
	} catch (Exception e) {
		e.printStackTrace();
	    throw new RuntimeException(e);
	}
    }

    private Evaluation createEvaluation() {
	try {
	    Evaluation eval = new Evaluation(filteredTestSet);
	    return eval;
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    /**
     * @deprecated
     * @param outFile
     * @throws Exception
     * 
     * This is slower than Hunni's method in 
     */
     
    public void createOutputFromEvaluation(String outFile) throws Exception {
	StringBuffer buffer = new StringBuffer();
	evaluation.evaluateModel(classifier, filteredTestSet, buffer, null,
		true); // TODO doesn't this evaluate the whole thing? and then
		       // below we do it again for every single instance?
	

	
	
	File file = new File(outFile);
	if (!file.exists()) {
	    file.createNewFile();
	}
	BufferedWriter writer = new BufferedWriter(new FileWriter(file));
	writer.append(buffer.toString());
	writer.close();
    }

    public TMHResultMap createResultMap() throws Exception
    {    	
    	TMHResultMap result = new TMHResultMap();
    	System.out.print("creating resultmap for "+testSet.numInstances() +" instances:");
		
		
    	for (int i = 0; i < testSet.numInstances(); i++)
		{
			if(i % 500 == 0) {
				System.out.print('.');
			}
			
		    double[] values = new double[3];

		    values[TMHResultMap.OBSERVED] = invertValue(filteredTestSet.instance(i).value(filteredTestSet.attribute("class")));	//observed
		    values[TMHResultMap.CONFIDENCE] = classifier.distributionForInstance(filteredTestSet.instance(i))[0];
		    
		    if (values[TMHResultMap.CONFIDENCE] > 0.5) {
		    	values[TMHResultMap.PREDICTED] = PARSEDSYMBOLTMHRESIDUE;
		    } else {
		    	values[TMHResultMap.PREDICTED] = PARSEDSYMBOLNONTMHRESIDUE;
		    }
		    		
		    result.put(testSet.instance(i).stringValue(0), values.clone());
		}
    	
    	System.out.println();

		return result;
    }
    
    public double getGamma()
    {
    	return this.classifier.getGamma();
    }
    
    public double getcost()
    {
    	return this.classifier.getCost();
    }

    private double invertValue(double value) {
    	return Math.abs(value - 1);
    }

    /**
     * @deprecated
     * @param outputPath
     * @throws Exception
     * 
     * Deprecated since it uses the confusion matrix from evaluate and in addition 'createResultMap'
     */
    public void createOutputFromClassifier(String outputPath) throws Exception {
	File file = new File(outputPath);
	if (!file.exists()) {
	    file.createNewFile();
	}
	BufferedWriter writer = new BufferedWriter(new FileWriter(file));
	for (int i = 0; i < testSet.numInstances(); i++) {
	    double[] field = classifier.distributionForInstance(filteredTestSet
		    .instance(i));
	    String test = testSet.instance(i).stringValue(0)
		    + ": "
		    + filteredTestSet.instance(i).value(
			    filteredTestSet.attribute("class")) + ","
		    + classifier.classifyInstance(filteredTestSet.instance(i))
		    + "(" + field[0] + ";" + field[1] + ")";
	    writer.append(test);
	    writer.newLine();
	}
	writer.newLine();
	double[][] matrix = evaluation.confusionMatrix();
	for (int i = 0; i < 2; i++) {
	    writer.append(matrix[0][i] + ";" + matrix[1][i]);
	    writer.newLine();
	}
	writer.close();
    }

    public void saveModel(String modelPath) throws Exception {
	Vector<LibSVM> vector = new Vector<LibSVM>();
	vector.add(classifier);
	SerializationHelper.write(modelPath, vector);
    }

    @SuppressWarnings("unchecked")
    public void loadModel(String modelPath) {
	try {
	    classifier = ((Vector<LibSVM>) SerializationHelper.read(modelPath))
		    .get(0);
	} catch (Exception e) {
	    throw new RuntimeException(e);
	}
    }

    public Evaluation getEvaluation() {
	return evaluation;
    }
    
  //after the 1st SVM: filter out the nontransmembran instances, return 2 distinct sets:
   //one with the instanceset for svm2 one with the nontransmembrane instances which are to be added in the last step
    // the cutoff is the cutoff of the confidence sum used to separate transmembrane and soluble proteins
    public static Instances[] filterOutNonTMHInstances(HashMap<String, double[]> resultsFrom1SVM ,double cutoff, Instances set){
        Instances[] devidedInstanceSets = new Instances[2];
        devidedInstanceSets[TRANSMEMBRANE_INSTANCES] = new Instances(set);
        devidedInstanceSets[SOLUBLE_INSTANCES] = new Instances(set);
        HashMap<String,Double> TMHproteins = getAllTMHProteinsFromsvm1Output(resultsFrom1SVM,cutoff);
        //in progress
        String sProtein_pos;
        String name;
        int deletedTMH = 0;
        int	deletedSol = 0;
        for (int i = 0; i < set.numInstances(); i++){
        	sProtein_pos = set.instance(i).stringValue(0);
			name = sProtein_pos.substring(0,sProtein_pos.lastIndexOf("_"));
			if (TMHproteins.containsKey(name)){
				devidedInstanceSets[SOLUBLE_INSTANCES].delete(i-deletedTMH);
				deletedTMH++;
			} else {
				devidedInstanceSets[TRANSMEMBRANE_INSTANCES].delete(i-deletedSol);
				deletedSol++;
			}
        }
        
        return devidedInstanceSets;
    }

    private static HashMap<String, Double> getAllTMHProteinsFromsvm1Output(HashMap<String, double[]> resultsFrom1SVM, double cutoff){
        HashMap<String,Double> TMHproteins = new HashMap<String,Double>();
        for (String key : resultsFrom1SVM.keySet()) {
        	double[] protein = resultsFrom1SVM.get(key);
        	double confidenceSum = protein[2];
        		if(SolTmhEvaluator.cutoff(cutoff,confidenceSum)){
        			TMHproteins.put(key,confidenceSum );
        		}
        }

        return TMHproteins;
    }

    
    // this attribut is only added to the tesSet!!! the filtering etc. has to be redone
    public static Instances insertConfidence3SVM (HashMap<String, double[]> result2svm, Instances set){
 	    //System.out.println(set.numAttributes());
    	//FastVector v = new FastVector(this.testSet.numInstances());
    	Attribute att = new Attribute("ConfidenceSvm2_0");
 	    set.insertAttributeAt(att, 1);
    	for (int i = 0; i < set.numInstances(); i++){
    		double conf = result2svm.get(set.instance(i).stringValue(0))[2];   	
    	    set.instance(i).setValue(1, conf); 
    	}
 	    //System.out.println(set.numAttributes());
    	return set;
    }

    // this attribut is only added to the tesSet!!! the filtering etc. has to be redone
    public static Instances insertConfidence3SVM_window (HashMap<String, double[]> result2svm, Instances set, int windowSize){
 	    //System.out.println(set.numAttributes());
    	//FastVector v = new FastVector(this.testSet.numInstances());
 	    
 	    
		int nameLength = 0;
		String name = "";
		int pos = 0;
		
		int windowOffset = (windowSize-1)/2;
		
		int neighbourPos = 0;
		double neighbourProp = 0;
		String neighbourName = "";
		
		Attribute att;
		
		for(int i = -windowOffset; i <= windowOffset; i++) {
			att = new Attribute("ConfidenceSvm2_"+String.valueOf(i));
			set.insertAttributeAt(att,getAttPosToPositionInWindow(i,windowOffset));
		}

		String sProteinName_pos;
		
		// jede instanz durchlaufen
	    for (int instId = 0; instId < set.numInstances(); instId++) {

	    	sProteinName_pos = set.instance(instId).stringValue(0);
			
			nameLength = sProteinName_pos.lastIndexOf("_");
			name = sProteinName_pos.substring(0,nameLength);
			pos = Integer.parseInt(sProteinName_pos.substring(nameLength+1));
			

			// alle nachbarn der aktuellen instanz durchlaufen
			for(int i = 0; i < windowSize; i++) {
				neighbourPos = pos - windowOffset + i;
				neighbourName = name+"_"+String.valueOf(neighbourPos);
				if(result2svm.containsKey(neighbourName)) {
					neighbourProp = result2svm.get(neighbourName)[2]; 
				}else {
					neighbourProp = 0;
				}
				
	    	    set.instance(instId).setValue(i+1, neighbourProp); 
			}
 	   
 	   }
 	    
    	
 	    //System.out.println(set.numAttributes());
    	return set;
    }
    
    
    // w�re wohl nicht n�tig gewesen das auszulagern ^^
    private static int getAttPosToPositionInWindow(int posInWindow, int offset) {
    	return posInWindow+offset+1;
    }

    
}
